/*
 * server.c
 * Authors: Doris, Steph
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define MAX_LINE 256

struct user {
	char * username;
	int sock;
	struct user *next;
	struct user *prev;
};

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char * argv[]) {
    fd_set master;    // master file descriptor list
    fd_set read_fds;  // temp file descriptor list for select()
    int fdmax;        // maximum file descriptor number
    int listener;     // listening socket descriptor aka server's socket
    int newfd;        // newly accept()ed socket descriptor
    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;

    const char * server_port;
    char buf[MAX_LINE];    // buffer for client data
    struct user * first;
    struct user * ulist;
    int nbytes;
    int numusers = 0;

    char remoteIP[INET6_ADDRSTRLEN];

    int yes=1;        // for setsockopt() SO_REUSEADDR, below
    int i, j, rv;

    struct addrinfo hints, *ai, *p;

    if(argc == 2) {
    	server_port = argv[1];
    } else {
    	fprintf(stderr, "Chat Server: Not enough arguments\n");
    	exit(0);
    }

    first = NULL;
    ulist = NULL;

    FD_ZERO(&master);    // clear the master and temp sets
    FD_ZERO(&read_fds);

    // get us a socket and bind it
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if ((rv = getaddrinfo(NULL, server_port, &hints, &ai)) != 0) {
        fprintf(stderr, "select Server: %s\n", gai_strerror(rv));
        exit(1);
    }

    for(p = ai; p != NULL; p = p->ai_next) {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (listener < 0) {
            continue;
        }

        // lose the pesky "address already in use" error message
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
            close(listener);
            continue;
        }

        break;
    }

    // if we got here, it means we didn't get bound
    if (p == NULL) {
        fprintf(stderr, "select server: failed to bind\n");
        exit(2);
    }

    freeaddrinfo(ai); // all done with this

    // listen
    if (listen(listener, 10) == -1) {
        perror("listen");
        exit(3);
    }
    printf("Server Started.\n");
    // add the listener to the master set
    FD_SET(listener, &master);

    // keep track of the biggest file descriptor
    fdmax = listener; // so far, it's this one

    // main loop
    while(1) {
        read_fds = master; // copy it
        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select");
            exit(4);
        }

        // run through the existing connections looking for data to read
        for(i = 0; i <= fdmax; i++) {
            if (FD_ISSET(i, &read_fds)) { // we got one!!
                if (i == listener) {
                    // handle new connections
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr,
                        &addrlen);

                    if (newfd == -1) {
                        perror("accept");
                    } else {
                        FD_SET(newfd, &master); // add to master set
                        if (newfd > fdmax) {    // keep track of the maximum
                            fdmax = newfd;
                        }
                        printf("selectserver: new connection from %s on "
                            "socket %d\n",
                            inet_ntop(remoteaddr.ss_family,
                                get_in_addr((struct sockaddr*)&remoteaddr),
                                remoteIP, INET6_ADDRSTRLEN),
                            newfd);
                    }
                } else {
                    // handle data from a client
                    if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) {
                        // got error or connection closed by client
                        if (nbytes == 0) {
                            // connection closed
                            printf("selectserver: socket %d hung up\n", i);
                        } else {
                            perror("recv");
                        }
                        close(i); // bye!
                        struct user* rUser;
                        ulist = first;
                        FD_CLR(i, &master); // remove from master set
//                        if(first != NULL && first->next == NULL) {
//                        	if(first->sock == i) {
//                        		free(first);
//                        	}
//                        }
                        while(ulist != NULL) {
                        	if(ulist->sock == i) {
                        		rUser = ulist;
                        		free (rUser->username);

                        		if (rUser->prev != NULL)
                        			rUser->prev->next = rUser->next;
                        		if (rUser->next != NULL)
                        			rUser->next->prev = rUser->prev;

                        		rUser->next = NULL;
                        		rUser->prev = NULL;
                        		free(rUser);
                        		numusers--;
                        		if (numusers == 0) {
                        			first = NULL;
                        			ulist = NULL;
                        		}

                        		break;
                        	}
                        	ulist = ulist->next;
                        }
                    } else {
												//data for all types of messages
                        char msgType = buf[0];
                        char *uName;
                        char response2B[2];
                        struct user * newuser;
                        struct user * lastUser;

                        //data used for sendTo
												int sendsock = 0;
												char * fromUser;
												fromUser = NULL;
												int tLen = 0;
												char text;

												if (msgType == 0x03) {
													tLen = (buf[21] << 8) + buf[22];
												}
												char sendToResponse[23+tLen];

												// data used for list
												char listResponse[(20*numusers)+3];
                        // data used for logout
                        struct user *rUser;
                        struct user *cUser;
                        ulist = first;
												// for loop init
                        int c, k;

                        switch(msgType) {
                        	case 0x01:	//login
                        		response2B[0] = 0x05;

                        		//ulist = first;
                        		newuser = malloc(sizeof(struct user));
                        		if(strlen(buf) > 21) {
                        			perror("Malformed package");
                        			response2B[1] = 0x02;
                        			send(i, response2B, 2, 0);
                        			close(i);
                        			FD_CLR(i, &master); //close and remove
                        			break;
                        		}

                        		for(c=0; c<21; c++) {
                        			if((buf[c+1] == ' ') ||
                        				(buf[c+1] == '\t') ||
                        				(buf[c+1] == '\n')||
                        				(buf[1] == '\0')) {
                        					perror("Malformed package->Failure");
                        					response2B[1] = 0x03;
                        					send(i, response2B, 2, 0);
                        					close(i);
                        					FD_CLR(i, &master); //close and remove
                        					break;
                        			}
                        		}
//                        	if(uName[0] == '\0') {
//                        			response2B[1] = 0x03;
//                        			send(i, response2B, 2, 0);
//                        			break;
//                        	}
                        		// all tests passed -> username is valid
                        		uName = (char *) malloc (20*sizeof(char));
														for (c=0; c<21; c++) {
															*(uName+c) = buf[c+1];
														}

														c = 1; // checks for errors with username on server
                        		while(ulist != NULL) {
                        			if(strcmp(ulist->username, uName) == 0) { // existing username
                        				perror("Username already in use\n");
                        				free(uName);
                        				free(newuser);
                        				c = 0;
                        				break;
                        			} else if(ulist->sock == i) { // client already logged in
                        				perror("Already logged in\n");
                        				free(uName);
                        				free(newuser);
                        				c = 0;
                        				break;
                        			} else {
                        				lastUser = ulist;
                        				ulist = ulist->next;
                        			}
                        		}
														// building newUser's data
                        		if (c) {
															newuser->username = uName;
															newuser->sock = i;
															newuser->next = NULL;

															if(first == NULL) {// no current users
																newuser->prev = NULL;
																first = newuser;
																ulist = first;
															} else {
																newuser->prev = lastUser;
																lastUser->next = newuser;
															}
															printf("New User Logged In.\n");
															numusers++;
															response2B[1] = 0x01;
															send(i, response2B, 2, 0);
															break;
                        		}
                        		else {
                      				response2B[1]=0x03;
                      				send(i, response2B, 2, 0);
                      				break;
                        		}

                        	case 0x02:	//list
                        		listResponse[0] = 0x02;
                        		//response[1] and response[2] are calculated from numusers
                        		for(c = 0; c < numusers; c++) {
                        			for (k=0; k< 20; k++){
                        				listResponse[(20*c)+k+3] = *((ulist->username)+k);
                        			}
                        			//responseXB[c+3] = ulist->username;
                        			ulist = ulist->next;
                        		}

                        		listResponse[1] = (numusers & 0xFF00)>> 8;
                        		listResponse[2] = (numusers & 0x00FF);
                        		send(i, listResponse, 3+20*numusers, 0);
                        		break;

                        	case 0x03:	//sendto
                        		response2B[0] = 0x05;
                        		uName = (char*) malloc (20*sizeof(char));
                        		for (c=0; c<21; c++) {
                        			if ((buf[c+1] == ' ') ||
                        					(buf[c+1] == '\t') ||
                        					(buf[c+1] == '\n')) {
                        				response2B[1] = 0x02;
                        				send(i, response2B, 2, 0);
                        				free(uName);
                        				break;
                        			}
                        			*(uName+c) = buf[c+1];
                        		}

                        		if (first == NULL) {
                        			response2B[1] = 0x03;
                        			send(i, response2B, 2, 0);
                        			free(uName);
                        			break;
                        		} else {
                        			//ulist = first;
                        			while (ulist != NULL) {
                        				if (strcmp(ulist->username, uName) == 0) {
                        					sendsock = ulist->sock;
                        				}
                        				if(ulist->sock == i) {
                        					fromUser = ulist->username;
                        				}
                        				ulist = ulist->next;
                        			}
                        			if ((sendsock == 0) || (fromUser == NULL)) {
                        				response2B[1] = 0x03;
                        				send(i, response2B, 2, 0);
                        				//close(i);
                        			} else {
                        				for (c=0; c<21; c++) {
                        					buf[c+1] = fromUser[c];
                        				}

                        				if(send(sendsock, buf, tLen+23,0) < 0) {
                        					response2B[1] = 0x03;
                        					send(i, response2B, 2, 0);
                        				}
                        				response2B[1] = 0x01;
                        				send(i, response2B, 2, 0);
                        			}
                        		}
                        		free(uName);
                        		break;

                        	case 0x04:	//logout
                        		cUser = ulist;
                        		c = 0; // c = 0 if username not found

                        		// check if user is currently logged in
                        		while (cUser != NULL) {
                        			if (cUser->sock == i) { // found existing username
                        				response2B[1] = 0x01; // success
                        				rUser = cUser;

                        				// reorganize lists
                        				if (cUser->next != NULL) // cUser is not last user on ulist
                        					cUser->next->prev = rUser->prev;

                        				if (rUser->prev != NULL)  // cUser is not first on list
                        					rUser->prev->next = cUser->next;


                        				// cleaning up memory
                        				free(rUser->username);
                        				rUser->prev = NULL;
                        				rUser->next = NULL;
                        				free(rUser);
                        				c = 1;
                        				numusers--;
                        				if (numusers == 0) {
                        					ulist = NULL;
                        					first = NULL;
                        				}
                        				perror("User has logged out.\n");
                        				break;
                        			}

                        			cUser = cUser->next;
                        		}
                        		response2B[0] = 0x05;
                        		if (!c) {
                        			perror("User Does Not Exist.\n");
                        			response2B[1]=0x03;
                        		}
                        		send(i, response2B, 2, 0);
                        		break;

                        	default:	//else
                        		break;
                        } //end switch
                    }
                } // END handle data from client
            } // END got new incoming connection
        } // END looping through file descriptors
    } // END while(1)--and you thought it would never end! ... hm.... does it...??

    close(listener);
    return 0;
}

